using System;
using System.Collections.Generic;
using System.Linq;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Samplers
{
    public class AdaptiveProgressiveSampler : ISampler
    {
        public const double ErrorStop = 0.0002;
        public const double ErrorSplit = 256.0 * ErrorStop;

        public const int SmallSize = 4;
        public const int BigSize = 64;

        public static float SamplesPerBudget = 1.1f;
        public static int EvaluationPasses = 1;


        public class SmallTile
        {
            public int Nx, Ny;
            public float Error;
        }

        public class BigTile : SmallTile
        {
            public List<SmallTile> Children;
            public int SampleBudget;
            private int samples;

            public bool Done
            {
                get { return (BigSize * BigSize) * SampleBudget <= samples; }
            }
            public BigTile()
            {
                samples = 0;
                Children = new List<SmallTile>();
            }

            public Sample SampleTile(Sample wmsample)
            {

                var rx = wmsample.GetLazyValue();
                var ry = wmsample.GetLazyValue();
                wmsample.imageX = Nx + rx * BigSize;
                wmsample.imageY = Ny + ry * BigSize;
                samples++;
                return wmsample;
            }
        }

        private SmallTile[] smallTiles;
        private BigTile[] bigTiles;

        public int Width { get; protected set; }
        public int Height { get; protected set; }

        public int screenWidth, screenHeight, screenStartLine;
        public int pass;
        public bool lowLatency;
        public int valuesPerSample;

        private IRandomProvider rnd;

        public int tIdx;
        private bool warmUpcomplete;
        protected int _currentSampleScreenY, currentBlockSample;
        internal AdaptiveImageFilm Film;

        public AdaptiveProgressiveSampler()
        {
            rnd = new FastRandom();
        }

        private void InitSampler()
        {

            Tracer.TraceLine("Init adaptive sampling, {0} generation", generation);
            blockIndex = 0;
            var small = new List<SmallTile>();
            var big = new List<BigTile>();
            BigTile current = new BigTile() { Nx = 0, Ny = 0 };

            int i = 0, nx = 0, ny = 0;
            //for (int i = 0, nx = 0, ny = 0; i < screenHeight; i += 4)
            while (i< screenHeight-4)
            {
                for (int j = 0; j < screenWidth-4; j += 4)
                {
                    float error = 0f;
                    int nonBlack = 0;
                    for (int k = 0; k < 4; k++)
                    {
                        for (int l = 0; l < 4; l++)
                        {
                            error += Film.RelativeVariance(j + l, i + k);
                            if (!Film.Black(j + l, i + k))
                            {
                                nonBlack++;
                            }
                        }
                    }
                    if (nonBlack == 0)
                        nonBlack = 1;
                    error = (float)Math.Sqrt(error / nonBlack);

                    var sm = new SmallTile() { Nx = j, Ny = i, Error = error };
                    current.Error = Math.Max(current.Error, error);
                    small.Add(sm);
                     current.Children.Add(sm);
                     if ((j > 63 && (j % 64) == 0) && i > 63 && (i % 64) == 0)
                    {
                        nx = j;
                        big.Add(current);
                        current = new BigTile() { Nx = nx, Ny = ny, Error = 0f };
                    }
                }
                i++;

                if (i > 63 && (i % 64) == 0)
                {
                    ny = i;
                    //big.Add(current);
                    //current = new BigTile() { Nx = nx, Ny = ny, Error = 0f };
                }
            }
            float maxError = big.Max(item => item.Error);
            float minError = big.Where(b => b.Error > 0f).Min(item => item.Error);

            foreach (var bigTile in big)
            {
                if (bigTile.Error < 1)
                {
                    bigTile.SampleBudget = 1;
                }
                else
                {
                    bigTile.SampleBudget = Math.Max(1,
                        (int)Math.Round(SamplesPerBudget * ((1 - minError) + (bigTile.Error / maxError))));
                }
                if (
                    //(bigTile.Nx == screenWidth && bigTile.Ny == screenHeight) ||
                    (bigTile.Nx > screenWidth && bigTile.Ny > screenHeight))
                {
                    bigTile.SampleBudget = 0;
                }

            }
            big = big.Distinct(new BigTileComparer()).ToList();

            big.Sort((a, b) => a.SampleBudget.CompareTo(b.SampleBudget));
            big.Reverse();

            this.bigTiles = big.ToArray();
            this.smallTiles = small.ToArray();
            Tracer.TraceLine("Start adaptive sampling, {0} generation [{1:F5} max error, {2} max budget]", generation, maxError, big[0].SampleBudget);
            generation++;

        }

        class BigTileComparer : IEqualityComparer<BigTile>
        {
            public bool Equals(BigTile x, BigTile y)
            {
                return x.Nx == y.Nx &&
                       x.Ny == y.Ny &&
                       x.SampleBudget == y.SampleBudget;
            }

            public int GetHashCode(BigTile obj)
            {
                return obj.Nx.GetHashCode() ^
                    obj.Ny.GetHashCode() ^
                    obj.Error.GetHashCode() ^
                    obj.Children.GetHashCode();
            }
        }

        private int passCounter = 0;
        private int blockIndex = 0, generation;

        public Sample GetSample(Core.Types.Sample prevSample = null)
        {
            TotalSamples++;
            passCounter++;
            var wmsample = new Sample(this);

            if (!warmUpcomplete)
            {
                var rx = wmsample.GetLazyValue();
                var ry = wmsample.GetLazyValue();
                wmsample.imageX = rx * screenWidth;
                wmsample.imageY = ry * screenHeight;
                if (passCounter == (screenWidth * screenHeight))
                {
                    passCounter = 0;
                    pass++;
                }

                warmUpcomplete = TotalSamples >= screenWidth * screenHeight * EvaluationPasses;
                if (warmUpcomplete)
                {
                    this.InitSampler();
                }

                return wmsample;
            }

        @newblock:
            var block = bigTiles[blockIndex];
            if (block.Done)
            {
                blockIndex++;
                if (blockIndex == bigTiles.Length - 1)
                {
                    pass += (int)SamplesPerBudget;
                    this.InitSampler();
                }
                goto @newblock;
            }

            var sample = block.SampleTile(wmsample);
            if (sample.imageX >= screenWidth)
            {
                sample.imageX = screenWidth;
            }
            if (sample.imageY >= screenHeight)
            {
                sample.imageY = screenHeight;
            }

            return sample;
        }

        public float GetLazyValue(Core.Types.Sample sample)
        {
            return rnd.NextFloat();
        }

        public float GetLDValue(Core.Types.Sample sample)
        {
            return rnd.NextFloat();
        }

        public float NextFloat()
        {
            return rnd.NextFloat();
        }

        public void Init(int width, int height)
        {
            rnd.Reinitialise((int)DateTime.Now.Ticks);
            if (screenWidth == 0 && screenHeight == 0)
            {
                Width = screenWidth = width;
                Height = screenHeight = height;
                //screenWidth = width;
                //screenHeight = height;
            }
            screenStartLine = Math.Max(0, screenStartLine);
            _currentSampleScreenY = Math.Max(0, screenStartLine);

            pass = 0;
            TotalSamples = 0;
            warmUpcomplete = false;
        }

        public int GetPass()
        {
            return pass;
        }


        public long TotalSamples
        {
            get;
            set;

        }

        public float GetLazyValue(Sample sample, int index)
        {
            return sample.sampleData[unchecked(index)];
        }

    }
}